


local skynet = require "skynet"
local ex_log = require "ex_log"



return function(parent, self_name)
    local childmap = {}
    local classmap = {}
    local need_save_map = {}
    local need_save = false

    local inited_map = {}
    local inited_coroutines = {}

    local M = {
        player = parent.player,
        parent = parent,
        name = self_name,
    }
    M.top = parent.top or M

    function M.init()
    end
    function M.on_save()
    end

    function M.reg_child(name, class)
        assert(type(class) == "function", name)
        assert(not classmap[name], name)
        classmap[name] = class
    end

    function M.get_child(name)
        local child = childmap[name]
        if child then
            return child
        end
        local class = classmap[name]
        assert(class, name)
        if inited_map[name] then
            local co = coroutine.running()
            table.insert(inited_coroutines, co)
            skynet.wait()
            child = childmap[name]
            assert(child, name)
        else
            inited_map[name] = true
            child = class(M, name)
            assert(child, name)
            child.init()
            childmap[name] = child
            for i, v in ipairs(inited_coroutines) do
                skynet.fork(function()
                    skynet.wakeup(v)
                end)
            end
            inited_map = {}
            inited_coroutines = {}
        end
        return child
    end

    function M.get_mod(path)
        local mod = M.top
        for i, v in ipairs(path) do
            mod = mod.get_child(v)
        end
        return mod
    end

    function M.get_parent()
        return M.parent
    end

    function M.get_top()
        return M.top
    end

    function M.have_child(name)
        return childmap[name]
    end

    function M.have_mod(path)
        local mod = M.top
        for i, v in ipairs(path) do
            mod = mod.get_child(v)
            if not mod then
                return nil
            end
        end
        return mod
    end

    function M.call_mod(path, fname, ...)
        local child = M.get_mod(path)
        assert(child, path)
        local fn = child[fname]
        assert(fn, fname)
        return fn(...)
    end

    function M.check_save()
        if need_save then
            local ret = xpcall(M.on_save, ex_log.error)
            if ret then
                need_save = false
            end
        end

        if next(need_save_map) then
            local saved = {}
            for k, _ in pairs(need_save_map) do
                local ret = xpcall(M.on_save, ex_log.error, k)
                if ret then
                    table.insert(saved, k)
                end
            end
            for i, v in ipairs(saved) do
                need_save_map[v] = nil
            end
        end
        for k, v in pairs(childmap) do
            v.check_save()
        end
    end

    function M.save(flag)
        if flag then
            need_save_map[flag] = true
        else
            need_save = true
        end
    end

    function M.call_children(fname, ...)
        local fn = M[fname]
        if fn then
            xpcall(fn, ex_log.error, ...)
        end
        for k, v in pairs(childmap) do
            v.call_children(fname, ...)
        end
    end

    function M.get_children(all)
        if not all then
            return childmap
        else
            local children = {}
            for k, v in pairs(classmap) do
                children[k] = M.get_child(k)
            end
            return children
        end
    end

    function M.notify(...)
        if M.player.event then
            M.player.event.notify(...)
        end
    end

    function M.attach(...)
        if M.player.event then
            M.player.event.attach(...)
        end
    end

    function M.detach(...)
        if M.player.event then
            M.player.event.detach(...)
        end
    end

    return M
end
